package com.chery.foxlogrecord.core.aop;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.chery.foxlogrecord.core.annotation.LogRecord;
import com.chery.foxlogrecord.core.annotation.LogRecordModel;
import com.chery.foxlogrecord.core.enums.LogOperate;
import com.chery.foxlogrecord.core.pojo.ObjectDiffDTO;
import com.chery.foxlogrecord.core.service.ObjectDiffService;
import com.chery.foxlogrecord.core.util.AopUtil;
import com.chery.foxlogrecord.core.util.ObjectDiffUtil;
import com.chery.foxlogrecord.core.util.SpelUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Map;
import java.util.TreeMap;

/**
 * @author qiangt
 * @date 2023/9/13
 * @apiNote
 */
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class LogRecordAop {

    /** 从返回值中取业务id的spel表达式前缀 */
    private static final String RETURN_SPEL_PREFIX = "result";
    /** spel表达式工具类，用来执行spel获取结果 */
    private final SpelUtil spelUtil;

    private final ObjectDiffService objectDiffService;
    /** 数据库或空对象（旧对象） */
    private Object oldObject = null;
    /** 业务主键 */
    private Object businessKey = null;
    /** 接口参数对象 */
    Map<String, Object> paramMap = new TreeMap<>();

    /** 切点定义 */
    @Pointcut("@annotation(com.chery.foxlogrecord.core.annotation.LogRecord)")
    public void pointcut() {

    }

    /** 切面处理 */
    @Around(value = "pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        LogRecord logRecord = method.getAnnotation(LogRecord.class);
        Object result = null;
        if (StrUtil.isEmpty(logRecord.key())) {
            log.error("记录操作日志@LogRecord配置错误，请设置业务主键key的取值规则");
            result = joinPoint.proceed();
            return result;
        }
        try {
            fillBusinessKeyAndOldObject(joinPoint, logRecord);
        } catch (Exception e) {
            log.error("操作记录aop，执行切面方法前异常：{}", e.getMessage(), e);
        }

        // 执行切面方法
        result = joinPoint.proceed();

        try {
            if (isInsertRecord(logRecord.key())) {
                // 如果spel是#result.xxx，则从返回值中获取业务主键key，且是新增业务数据
                businessKey = spelUtil.executeExpression(logRecord.key(), RETURN_SPEL_PREFIX, result);
            }
            if (ObjectUtil.isEmpty(businessKey)) {
                return result;
            }
            sendObjectDiffDTO(method, logRecord);
        } catch (Exception e) {
            log.error("操作记录aop，执行切面方法后异常：{}", e.getMessage(), e);
        }
        return result;
    }

    /**
     * 是否新增数据
     * @param keySpel 业务主键key的spel表达式
     * @return true: 新增数据，false: 修改数据
     */
    private boolean isInsertRecord(String keySpel) {
        return keySpel.indexOf(RETURN_SPEL_PREFIX) > -1;
    }

    /**
     * 发送操作记录
     * @param method
     * @param logRecord
     */
    private void sendObjectDiffDTO(Method method, LogRecord logRecord) {
        Object paramObj = getLogRecordModelObj(method, paramMap);
        ObjectDiffDTO objectDiffDTO = ObjectDiffUtil.objectDiff(oldObject, paramObj);
        if (ObjectUtil.isNotEmpty(objectDiffDTO) && CollectionUtil.isNotEmpty(objectDiffDTO.getFieldDiffDTOList())) {
            objectDiffDTO.setBusinessId(Convert.toStr(businessKey));
            objectDiffDTO.setDescription(logRecord.desc());
            objectDiffDTO.setJsonBefore(JSON.toJSONString(oldObject));
            objectDiffDTO.setJsonAfter(JSON.toJSONString(paramObj));
            objectDiffDTO.setLogOperate(LogOperate.getLogOperateByCode(logRecord.operateType()));
            objectDiffDTO.setModule(logRecord.module());
            log.info("发送操作记录：{}", JSON.toJSONString(objectDiffDTO));
            objectDiffService.save(objectDiffDTO);
        }
    }

    /**
     * 填充业务主键和旧对象
     * @param joinPoint
     * @param logRecord
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    private void fillBusinessKeyAndOldObject(ProceedingJoinPoint joinPoint, LogRecord logRecord) throws InstantiationException, IllegalAccessException {
        paramMap = AopUtil.getParams(joinPoint);
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        Class returnType = method.getReturnType();

        if (isInsertRecord(logRecord.key())) {
            // 如果取业务id的spel包含result，则是新增，使用反射创建个新对象
            oldObject = returnType.newInstance();
        } else {
            // 如果取业务id的spel不包含result，则是更新，oldObject从数据库中取
            businessKey = spelUtil.executeExpression(logRecord.key(), paramMap);
            // spel执行spring service方法查询数据库记录
            Object oldObjectTemp = spelUtil.executeMethodExpression(logRecord.method(), Convert.toStr(businessKey));
            oldObject = BeanUtil.toBean(oldObjectTemp, returnType);
        }
    }

    /**
     * 获取切面方法中，带LogRecordModel注解的对象
     * @param method
     * @param params
     * @return
     */
    private Object getLogRecordModelObj(Method method, Map<String, Object> params) {
        Parameter[] parameters = method.getParameters();
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        Object paramObj = null;
        for (int i = 0; i < parameters.length; i++) {
            String paramName = parameters[i].getName();
            for (int j = 0; j < parameterAnnotations[i].length; j++) {
                Annotation annotation = parameterAnnotations[i][j];
                if (annotation instanceof LogRecordModel) {
                    String argName = ((LogRecordModel) annotation).value();
                    paramObj = StrUtil.isNotBlank(argName) ? params.get(argName) : params.get(paramName);
                    break;
                }
            }
        }
        return paramObj;
    }

}
